package org.whh.automatic.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.*;
import java.util.concurrent.*;
import java.util.function.Consumer;

/**
 * 坐标捕获工具类
 * <p>
 * 该类用于捕获鼠标指针在屏幕上的坐标。
 * 工作原理是周期性地检测鼠标位置，当鼠标在同一位置停留指定时间（默认5秒）后，
 * 自动捕获该位置的坐标并通过回调函数返回。
 * <p>
 * 主要用于辅助用户在创建自动化任务时精确定位目标元素的位置。
 */
public class CoordinateCapture {

    /**
     * 日志记录器
     */
    private static final Logger logger = LoggerFactory.getLogger(CoordinateCapture.class);
    /**
     * 需要稳定的次数阈值
     * 50次 * 100毫秒 = 5秒，表示鼠标需要在同一位置停留5秒才算稳定
     */
    private static final int REQUIRED_STABLE_COUNT = 50;
    /**
     * 调度器，用于定期检查鼠标位置
     * 使用ScheduledExecutorService实现定时任务，以守护线程方式运行
     */
    private ScheduledExecutorService scheduler;
    /**
     * 当前运行的调度任务引用
     * 存储当前正在执行的调度任务，便于取消或管理
     */
    private ScheduledFuture<?> scheduledTask;
    /**
     * 结果回调函数
     * 当成功捕获坐标时，通过此回调函数通知调用者
     */
    private Consumer<Point> resultCallback;
    /**
     * 坐标更新回调函数
     * 当鼠标位置变化时，通过此回调函数实时通知调用者当前坐标
     */
    private Consumer<Point> updateCallback;
    /**
     * 上次检测到的鼠标位置
     * 用于与当前位置比较，判断鼠标是否稳定
     */
    private Point lastPosition;
    /**
     * 是否正在捕获状态标志
     * volatile确保多线程环境下的可见性
     */
    private volatile boolean capturing = false;
    /**
     * 同步锁
     * 用于阻塞式等待捕获完成
     */
    private CountDownLatch captureLatch;
    /**
     * 机器人实例
     * 用于模拟鼠标移动，验证捕获的坐标
     */
    private Robot robot;
    /**
     * 稳定计数器
     * 记录鼠标位置保持稳定的检测次数
     */
    private int stableCount = 0;

    /**
     * 构造函数
     *
     * @param resultCallback 坐标捕获成功后的回调函数，接收捕获到的Point对象
     */
    public CoordinateCapture(Consumer<Point> resultCallback) {
        this.resultCallback = resultCallback;
        try {
            // 创建Robot实例，用于验证坐标
            this.robot = new Robot();
        } catch (Exception e) {
            logger.error("创建Robot实例失败", e);
        }

        // 创建调度器，使用守护线程，这样在应用退出时会自动关闭
        scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
            Thread t = new Thread(r, "CoordinateCapture-Thread");
            t.setDaemon(true);
            return t;
        });
    }

    /**
     * 设置坐标更新监听器
     * <p>
     * 该监听器会在每次检测鼠标位置时被调用，可用于实时显示当前坐标
     *
     * @param callback 坐标更新时的回调函数，接收当前鼠标位置
     */
    public void setCoordinateUpdateListener(Consumer<Point> callback) {
        this.updateCallback = callback;
    }

    /**
     * 开始捕获鼠标坐标
     * <p>
     * 启动一个定时任务，每100毫秒检查一次鼠标位置
     * 当鼠标在同一位置保持稳定达到阈值时，捕获该坐标
     * synchronized确保线程安全，防止多次调用导致混乱
     */
    public synchronized void startCapture() {
        if (capturing) {
            logger.warn("已经在捕获鼠标坐标，请勿重复启动");
            return;
        }

        // 清理之前可能存在的任务
        if (scheduledTask != null && !scheduledTask.isDone()) {
            scheduledTask.cancel(true);
        }

        // 初始化状态变量
        capturing = true;
        stableCount = 0;
        lastPosition = null;
        captureLatch = new CountDownLatch(1);

        // 启动新的调度任务，每100毫秒执行一次检查
        scheduledTask = scheduler.scheduleAtFixedRate(() -> {
            try {
                checkMousePosition();
            } catch (Exception e) {
                logger.error("检查鼠标位置时发生错误", e);
            }
        }, 0, 100, TimeUnit.MILLISECONDS);

        logger.info("开始捕获鼠标坐标");
    }

    /**
     * 停止捕获
     * <p>
     * 取消定时任务，释放资源，通知等待线程
     * synchronized确保线程安全
     */
    public synchronized void stopCapture() {
        if (!capturing) {
            return;
        }

        capturing = false;

        // 取消调度任务
        if (scheduledTask != null) {
            scheduledTask.cancel(false);
            scheduledTask = null;
        }

        // 释放同步锁，通知等待线程
        if (captureLatch != null) {
            captureLatch.countDown();
            captureLatch = null;
        }

        logger.info("停止捕获鼠标坐标");
    }

    /**
     * 检查鼠标位置
     * <p>
     * 该方法会定期被调度器调用，用于检测鼠标位置的变化
     * 当鼠标位置保持稳定达到阈值时，完成捕获并通知回调
     */
    private void checkMousePosition() {
        if (!capturing) {
            return;
        }

        try {
            // 获取当前鼠标位置
            Point currentPosition = MouseInfo.getPointerInfo().getLocation();

            // 通知坐标更新，用于实时显示
            if (updateCallback != null) {
                updateCallback.accept(currentPosition);
            }

            // 如果是第一次检测，直接记录位置并返回
            if (lastPosition == null) {
                lastPosition = new Point(currentPosition);
                return;
            }

            // 检查鼠标位置是否稳定
            if (isSamePosition(currentPosition, lastPosition)) {
                // 增加稳定计数
                stableCount++;

                // 如果稳定计数达到阈值，完成捕获
                if (stableCount >= REQUIRED_STABLE_COUNT) {
                    Point result = new Point(currentPosition);

                    // 通知回调
                    if (resultCallback != null) {
                        resultCallback.accept(result);
                    }

                    // 停止捕获
                    stopCapture();
                }
            } else {
                // 位置发生变化，重置计数器
                stableCount = 0;
                lastPosition = new Point(currentPosition);
            }
        } catch (Exception e) {
            logger.error("检查鼠标位置时发生错误", e);
            stopCapture();
        }
    }

    /**
     * 比较两个坐标是否相同
     * <p>
     * 允许微小的浮动（2像素以内），减少误判
     *
     * @param p1 第一个坐标点
     * @param p2 第二个坐标点
     * @return 如果两点距离在阈值内，返回true；否则返回false
     */
    private boolean isSamePosition(Point p1, Point p2) {
        return Math.abs(p1.x - p2.x) <= 2 && Math.abs(p1.y - p2.y) <= 2;
    }

    /**
     * 验证坐标是否正确
     * <p>
     * 通过将鼠标移动到捕获的坐标位置，帮助用户确认坐标准确性
     *
     * @param coordinate 要验证的坐标点
     */
    public void verifyCoordinate(Point coordinate) {
        if (robot != null && coordinate != null) {
            robot.mouseMove(coordinate.x, coordinate.y);
        }
    }

}
